package com.leenmvc.core.dao.mysql.wrapper;

import cn.hutool.core.util.RandomUtil;
import com.leenmvc.core.annotation.FillField;
import com.leenmvc.core.base.BaseEntity;
import com.leenmvc.core.dao.communal.Wrapper;
import com.leenmvc.core.exception.BusinessException;
import com.leenmvc.core.init.Constant;
import com.leenmvc.core.utils.ClassUtil;
import com.leenmvc.core.utils.StringUtils;
import com.leenmvc.core.utils.collection.ArrayUtils;
import com.leenmvc.core.base.result.PageData;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 拼装复杂sql语句的类
 */
public class QueryWrapper extends Wrapper {

    private final static Logger logger = LoggerFactory.getLogger(QueryWrapper.class);

    public QueryWrapper() {
    }

    public QueryWrapper(Class<?> cls) {
        this.cls = cls;
        this.columns = ClassUtil.getColumns(cls);
    }

    /**
     * 该方法获取SQL语句
     *
     * @return
     */
    @Override
    public String getSql() {
        sql.setLength(0);
        String limitIt = Constant.LIMIT + this.limit[0] + Constant.COMMA + limit[1];
        String cacheSql = sqlCache.get(this.cls.getName() + Constant.AND + Arrays.toString(this.outFields) + Constant.AND + this.columns.toString() + Constant.AND + this.where + Constant.AND + this.orderBy + Constant.AND + limitIt);
        if(cacheSql == null) {
            getBasicSql();
            if (where.length() > 0) {
                sql.append(Constant.WHERE + where);
            }
            if (orderBy.length() > 0) {
                sql.append(Constant.BLANK_ORDER_BY + orderBy);
            }
            if (this.limit[0] != -1 && this.limit[1] != -1) {
                int[] limits = pageInfo(this.limit[0], this.limit[1]);
                sql.append(Constant.LIMIT).append(limits[0]).append(Constant.COMMA).append(limits[1]);
            }
            sqlCache.put(this.cls.getName() + Constant.AND + Arrays.toString(this.outFields) + Constant.AND + this.columns.toString() + Constant.AND + this.where + Constant.AND + this.orderBy + Constant.AND + limitIt, this.sql.toString());
            long endTime = System.nanoTime();
            return sql.toString();
        }
        long endTime = System.nanoTime();
        return cacheSql;
    }

    /**
     * 构建基础SQL语句
     */
    private void getBasicSql() {
        Field[] fields = this.outFields;
        Set<String> inFields = new HashSet<>();
        if (fields.length > 0) { // 多表连接
            sql.insert(0, Constant.SELECT + StringUtils.join(columns, Constant.COMMA));
            int sqlLen = sql.length();
            sql.append(Constant.FROM + BaseEntity.getTableName(cls));
            sql.append(" " + simpleName);
            String[] outClsFields = new String[0];
            for (Field field : fields) {
                FillField annoFillField = field.getAnnotation(FillField.class);
                switch (annoFillField.type()) {
                    case ONE_FIELD_IN:
                        outClsFields = oneFieldIn(inFields, outClsFields, field, annoFillField);
                        break;
                    case ONE_TO_ONE:
                        outClsFields = oneToOne(inFields, outClsFields, annoFillField);
                        break;
                    case ONE_TO_MULTIPLE:
                        outClsFields = oneToMulti(inFields, outClsFields, annoFillField);
                        break;
                }
            }
            sql.insert(sqlLen, Constant.COMMA + StringUtils.join(outClsFields, Constant.COMMA));
        } else { // 单表
            sql.insert(0, Constant.SELECT + StringUtils.join(columns, Constant.COMMA));
            sql.append(Constant.FROM + BaseEntity.getTableName(cls));
            sql.append(" " + simpleName);
        }
    }

    /**
     * 一对多SQL语句构建
     *
     * @param inFields
     * @param outColumnArr
     * @param annoFillField
     * @return
     */
    private String[] oneToMulti(Set<String> inFields, String[] outColumnArr, FillField annoFillField) {
        Class<?> outCls = annoFillField.entity();
        String inField = annoFillField.inField();
        String outCondition = annoFillField.outCondition();
        String outName = outCls.getSimpleName();
        if (!inFields.contains(inField)) {
            sql.append(Constant.LEFT_JOIN);
            sql.append(BaseEntity.getTableName(outCls));
            if(outName.equals(simpleName)) {
                outName = outName + RandomUtil.randomNumber();
            }
            sql.append(" " + outName);
            sql.append(Constant.BLANK_ON + simpleName + Constant.DOT + BaseEntity.humpLine(inField) + Constant.EQUAL_SIGN + outName + Constant.DOT + BaseEntity.humpLine(outCondition));
        }
        outColumnArr = ArrayUtils.arrayAddAll(outColumnArr, BaseEntity.getColumnsForSql(outCls));
        inFields.add(inField);
        return outColumnArr;
    }

    /**
     * 实体关联另外一个实体，为一对一的关系
     *
     * @param inFields
     * @param outColumnArr
     * @param annoFillField
     * @return
     */
    private String[] oneToOne(Set<String> inFields, String[] outColumnArr, FillField annoFillField) {
        Class<?> outCls = annoFillField.entity();
        String inField = annoFillField.inField();
        String outCondition = annoFillField.outCondition();
        String outName = outCls.getSimpleName();
        if (!inFields.contains(inField)) {
            sql.append(Constant.LEFT_JOIN);
            sql.append(BaseEntity.getTableName(outCls));
            if(outName.equals(simpleName)) {
                outName = outName + RandomUtil.randomNumber();
            }
            sql.append(" " + outName);
            sql.append(Constant.BLANK_ON + simpleName + Constant.DOT + BaseEntity.humpLine(inField) + Constant.EQUAL_SIGN + outName + Constant.DOT + BaseEntity.humpLine(outCondition));
        }
        outColumnArr = ArrayUtils.arrayAddAll(outColumnArr, BaseEntity.getColumnsForSql(outCls));
        inFields.add(inField);
        return outColumnArr;
    }

    /**
     * 实体关联一个字段到外表
     *
     * @param inFields
     * @param outColumnArr
     * @param field
     * @param annoFillField
     * @return
     */
    @NotNull
    private String[] oneFieldIn(Set<String> inFields, String[] outColumnArr, Field field, FillField annoFillField) {
        Class<?> outCls = annoFillField.entity();
        String outField = BaseEntity.humpLine(annoFillField.outField());
        String inField = annoFillField.inField();
        String outCondition = annoFillField.outCondition();
        String outName = outCls.getSimpleName();
        if (!inFields.contains(inField)) {
            sql.append(Constant.LEFT_JOIN);
            sql.append(BaseEntity.getTableName(outCls));
            if(outName.equals(simpleName)) {
                outName = outName + RandomUtil.randomNumber();
            }
            sql.append(" " + outName);
            sql.append(Constant.BLANK_ON + simpleName + Constant.DOT + BaseEntity.humpLine(inField) + Constant.EQUAL_SIGN + outName + Constant.DOT + BaseEntity.humpLine(outCondition));
        }
        outColumnArr = ArrayUtils.arrayAdd(outColumnArr, outName + Constant.DOT + outField + " " + BaseEntity.humpLine(field));
        inFields.add(inField);
        return outColumnArr;
    }

    /**
     * 多表联查
     *
     * @param
     * @return
     */
    @Override
    public Wrapper joinField(String field) {
        Field field1 = BaseEntity.getField(this.simpleName + Constant.DOT + field);
        this.outFields = ArrayUtils.arrayAdd(this.outFields, field1);
        this.oneFieldInFields = ArrayUtils.arrayAdd(this.oneFieldInFields, field1);
        this.outColumns.add(BaseEntity.humpLine(field));
        return this;
    }

    @Override
    public Wrapper joinOne(String field) {
        Field field1 = BaseEntity.getField(this.simpleName + Constant.DOT + field);
        this.outFields = ArrayUtils.arrayAdd(this.outFields, field1);
        this.oneToOneFields = ArrayUtils.arrayAdd(this.oneToOneFields, field1);
        return this;
    }

    @Override
    public Wrapper joinMul(String field) {
        Field field1 = BaseEntity.getField(this.simpleName + Constant.DOT + field);
        this.outFields = ArrayUtils.arrayAdd(this.outFields, field1);
        this.oneToMulFields = ArrayUtils.arrayAdd(this.oneToMulFields, field1);
        return this;
    }

    @Override
    public Wrapper and() {
        this.where.append(Constant.BLANK_AND);
        return this;
    }

    public Wrapper and(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        field = simpleName + Constant.DOT + field;
        this.where.append(Constant.BLANK_AND).append(field +Constant.QUESTION_EQU_MARK_SIGN);
        paramList.add(value);
        conditons.add(BaseEntity.humpLine(field) +Constant.QUESTION_EQU_MARK_SIGN);
        return this;
    }

    // or 连接符
    public Wrapper or() {
        this.where.append(" or ");
        return this;
    }

    // 使用 select * from user where name like '%jen%' or age > 20  and (sex = 2 and sc = 9)
    public Wrapper linkOr() {
        int index = this.where.lastIndexOf(conditons.get(conditons.size() - 1), this.where.length() - 1);
        this.where.insert(index, "(");
        this.where.append(" or)");
        return this;
    }

    // 如果传参数那么默认就是用等号连接
    public Wrapper or(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        field = simpleName + Constant.DOT + field;
        this.where.append(" or ").append(field +Constant.QUESTION_EQU_MARK_SIGN);
        paramList.add(value);
        conditons.add(BaseEntity.humpLine(field) +Constant.QUESTION_EQU_MARK_SIGN);
        return this;
    }

    public QueryWrapper count(Class<?> cls) {
        return this;
    }

    // order by
    public QueryWrapper orderBy(String field, String desc) {
        this.orderBy.setLength(0);
        this.orderBy.append(simpleName + Constant.DOT + BaseEntity.humpLine(field)).append(" ").append(desc);
        return this;
    }

    /**
     * 排除某个字段
     *
     * @param fields
     * @return
     */
    public QueryWrapper decField(String... fields) {
        for (String field : fields) {
            field = simpleName + Constant.DOT + BaseEntity.humpLine(field);
            this.columns.remove(field);
        }
        return this;
    }

    /**
     * 只显示某些字段
     *
     * @param fields
     * @return
     */
    public QueryWrapper incField(String... fields) {
        this.columns.clear();
        for (String field : fields) {
            field = simpleName + Constant.DOT + BaseEntity.humpLine(field);
            this.columns.add(field);
        }
        return this;
    }

    /**
     * 限制条数
     *
     * @param pageInfo
     * @return
     */
    public QueryWrapper limit(Integer... pageInfo) {
        if (pageInfo.length == 1) {
            this.limit[0] = pageInfo[0];
            this.limit[1] = PageData.DEFAULT_PAGESIZE;
        } else if (pageInfo.length == 0) {
            this.limit[0] = PageData.DEFAULT_PAGE;
            this.limit[1] = PageData.DEFAULT_PAGESIZE;
        } else if (pageInfo.length == 2) {
            this.limit[0] = pageInfo[0];
            this.limit[1] = pageInfo[1];
        } else {
            throw new BusinessException("参数错误！！");
        }
        return this;
    }

    /**
     * 等于
     */
    public QueryWrapper eq(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.eq(field, value);
        return this;
    }

    /**
     * 不等于
     *
     * @param field
     * @param value
     * @return
     */
    public QueryWrapper uEq(String field, Object value) {
        super.uEq(field, value);
        return this;
    }

    /**
     * 右边随意
     *
     * @param field
     * @param value
     * @return
     */
    public QueryWrapper likeR(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.likeR(field, value);
        return this;
    }

    /**
     * 左边随意
     *
     * @param field
     * @param value
     * @return
     */
    public QueryWrapper likeL(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.likeL(field, value);
        return this;
    }

    /**
     * 全like
     *
     * @param field
     * @param value
     * @return
     */
    public QueryWrapper like(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.like(field, value);
        return this;
    }

    /**
     * 根据数组中
     *
     * @param field
     * @param ins
     * @return
     */
    public QueryWrapper in(String field, Object[] ins) {
        if (ins == null) return this;
        super.in(field, ins);
        return this;
    }

    public <T> QueryWrapper in(String field, List<T> ins) {
        return this.in(field, ins.toArray());
    }

    // between
    public QueryWrapper between(String field, Object start, Object end) {
        setAnd();
        super.between(field, start, end);
        return this;
    }

    // <
    public QueryWrapper lt(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.lt(field, value);
        return this;
    }

    // >
    public QueryWrapper gt(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.gt(field, value);
        return this;
    }

    // >=
    public QueryWrapper ge(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.ge(field, value);
        return this;
    }

    // <=
    public QueryWrapper le(String field, Object value) {
        if (StringUtils.isEmpty(value)) return this;
        super.le(field, value);
        return this;
    }

    // is null
    public QueryWrapper isNull(String field) {
        setAnd();
        super.isNull(field);
        return this;
    }

    // is not null
    public QueryWrapper isNotNull(String field) {
        setAnd();
        super.isNotNull(field);
        return this;
    }

    /**
     * 将hashMap转成QueryWrapper
     * like关键字 规则如下（前缀）：
     * LL_  左像（%在左边）
     * RL_  右像 (%在右边)
     * AL_  全像 (%在两边)  AL_username   %leen%
     * orderBY关键字规则如下 （前缀）：
     * OB_  排序的字段  value值为DESC 或 ESC    OB_createTime   DESC
     * page, pageSize 特殊字保留字 （只做分页功能）
     *
     * @return
     */
    public QueryWrapper mapToWrapper(Map<String, Object> params) {
        super.mapToWrapper(params);
        return this;
    }

    /**
     * 限制page
     *
     * @param page
     * @return
     */
    public QueryWrapper limitPage(int page) {
        this.limit[0] = page;
        return this;
    }

    /**
     * 限制page
     *
     * @param pageSize
     * @return
     */
    public QueryWrapper limitSize(int pageSize) {
        this.limit[1] = pageSize;
        return this;
    }

    @Override
    public void copy(Wrapper wrapper) {
        super.copy(wrapper);
    }

    /**
     * 获取分页信息
     *
     * @param pageInfo
     * @return
     */
    private int[] pageInfo(Integer... pageInfo) {
        int start = 0, pageSize = PageData.DEFAULT_PAGESIZE;
        if (pageInfo.length == 2) {
            start = (pageInfo[0] - 1) * pageInfo[1];
            pageSize = pageInfo[1];
        } else if (pageInfo.length == 1) {
            start = (pageInfo[0] - 1) * PageData.DEFAULT_PAGESIZE;
        }

        // 作最后的校准
        if (start < 0) start = 0;

        if (pageSize > PageData.MAX_PAGESIZE) {
            pageSize = PageData.MAX_PAGESIZE;
        } else if (pageSize < 0) {
            pageSize = 10;
        }
        return new int[]{start, pageSize};
    }

    @Override
    public QueryWrapper addSet(String setKey, Object value) {
        return null;
    }

}